/*
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */
package eu.bopet.jocadv.gui;

import com.jogamp.common.nio.Buffers;
import eu.bopet.jocadv.core.Component;
import eu.bopet.jocadv.core.Element;
import eu.bopet.jocadv.core.datum.CSys;
import eu.bopet.jocadv.core.datum.Point;
import eu.bopet.jocadv.core.elements.Line;
import java.nio.IntBuffer;
import java.util.Arrays;
import javax.media.opengl.GL2;
import javax.media.opengl.GLAutoDrawable;
import javax.media.opengl.GLEventListener;
import javax.media.opengl.glu.GLU;
import org.apache.commons.math3.geometry.euclidean.threed.Vector3D;

/**
 *
 * @author bocskapeter
 */
class GLRenderer implements GLEventListener {

    // Minimum canvas width
    static private final int MINIMUM_WIDTH = 32;
    // Minimum canvas height
    static private final int MINIMUM_HEIGHT = 24;
    // Selection buffer size
    private static final int SELECTION_BUFFER_SIZE = 512;
    // Selection window width
    private static final int SELECTION_WINDOW_X = 5;
    // Selection window height
    private static final int SELECTION_WINDOW_Y = 5;

    // Left side of the projection cut
    private double viewLeft = -16;
    // Right side of the projection cut
    private double viewRight = 16;
    // Bottom side of the projection cut
    private double viewBottom = -12;
    // Top side of the projection cut
    private double viewTop = 12;
    // Near side of the projection cut
    private double viewNear = 100;
    // Far side of the projection cut
    private double viewFar = -100;
    // GLCanvas width
    private int width;
    // GLCanvas height
    private int height;
    //Aspect ratio of the canvas
    private double aspectRatio;
    // handling the user inputs
    private InputHandler inputHandler;
    // rotation angle
    private double rAngle;
    // projection matrix
    private final double[] matrix;
    // view vector
    private final double[] viewVector3d;
    // 3D rotation axis
    private final double[] rotationAxis3d;
    // element renderer
    private final ElementRenderer elementRenderer;
    // selection buffer
    private IntBuffer selectionBuffer;
    // view port for selection
    private int viewport[];
    private final GLU glu;
    private Selection[] selection;
    private int hits;

    // processed component
    private final Component component;

    public GLRenderer(Component component) {
        this.width = MINIMUM_WIDTH;
        this.height = MINIMUM_HEIGHT;
        this.rotationAxis3d = new double[3];
        this.viewVector3d = new double[3];
        this.matrix = new double[16];
        elementRenderer = new ElementRenderer(this);
        selectionBuffer = Buffers.newDirectIntBuffer(SELECTION_BUFFER_SIZE);
        viewport = new int[4];
        glu = new GLU();

        this.component = component;

        // TODO delete below
        CSys cs1;
        Point p;
        //simulate user actions:
        p = Point.getNewPoint(new Vector3D(1, 1, 1));
        cs1 = CSys.getDefaultCSys(p.getPoint());
        component.addElement(p);
        component.addElement(cs1);
        Line line = new Line("Line");
        line.setEndPoint(Vector3D.ZERO, p.getPoint());
        component.addElement(line);
        System.out.println(component.toString());
        // TODO delete above
    }

    public InputHandler getInputHandler() {
        return inputHandler;
    }

    public void setInputHandler(InputHandler inputHandler) {
        this.inputHandler = inputHandler;
    }

    public double getrAngle() {
        return rAngle;
    }

    public void setrAngle(double rAngle) {
        this.rAngle = rAngle;
    }

    public int getWidth() {
        return width;
    }

    public void setWidth(int width) {
        this.width = width;
    }

    public int getHeight() {
        return height;
    }

    public void setHeight(int height) {
        this.height = height;
    }

    public double getViewLeft() {
        return viewLeft;
    }

    public void setViewLeft(double viewLeft) {
        this.viewLeft = viewLeft;
    }

    public double getViewRight() {
        return viewRight;
    }

    public void setViewRight(double viewRight) {
        this.viewRight = viewRight;
    }

    public double getViewBottom() {
        return viewBottom;
    }

    public void setViewBottom(double viewBottom) {
        this.viewBottom = viewBottom;
    }

    public double getViewTop() {
        return viewTop;
    }

    public void setViewTop(double viewTop) {
        this.viewTop = viewTop;
    }

    public double getViewNear() {
        return viewNear;
    }

    public void setViewNear(double viewNear) {
        this.viewNear = viewNear;
    }

    public double getViewFar() {
        return viewFar;
    }

    public void setViewFar(double viewFar) {
        this.viewFar = viewFar;
    }

    @Override
    public void init(GLAutoDrawable glDrawable) {
        final GL2 gl = glDrawable.getGL().getGL2();

        gl.glClear(GL2.GL_COLOR_BUFFER_BIT);
        gl.glClearColor(0, 0, 0, 0);
        gl.glShadeModel(GL2.GL_SMOOTH);

        gl.glClearDepth(1);
        gl.glEnable(GL2.GL_DEPTH_TEST);
        gl.glDepthFunc(GL2.GL_LEQUAL);
        gl.glHint(GL2.GL_PERSPECTIVE_CORRECTION_HINT, GL2.GL_NICEST);

        width = glDrawable.getSurfaceWidth();
        height = glDrawable.getSurfaceHeight();
        viewLeft = -width / 2;
        viewRight = width / 2;
        viewBottom = -height / 2;
        viewTop = height / 2;

        gl.glMatrixMode(GL2.GL_PROJECTION);
        gl.glLoadIdentity();
        gl.glOrtho(viewLeft, viewRight, viewBottom, viewTop, viewNear, viewFar);
        gl.glMatrixMode(GL2.GL_MODELVIEW);
        gl.glLoadIdentity();
        gl.glGetDoublev(GL2.GL_MODELVIEW_MATRIX, matrix, 0);

        viewVector3d[0] = matrix[2];
        viewVector3d[1] = matrix[6];
        viewVector3d[2] = matrix[10];

    }

    @Override
    public void dispose(GLAutoDrawable glDrawable) {
        final GL2 gl = glDrawable.getGL().getGL2();
    }

    @Override
    public void display(GLAutoDrawable glDrawable) {
        final GL2 gl = glDrawable.getGL().getGL2();

        gl.glClear(GL2.GL_COLOR_BUFFER_BIT);
        gl.glClear(GL2.GL_DEPTH_BUFFER_BIT);

        gl.glMatrixMode(GL2.GL_PROJECTION);
        gl.glLoadIdentity();
        gl.glOrtho(viewLeft, viewRight, viewBottom, viewTop, viewNear, viewFar);
        gl.glMatrixMode(GL2.GL_MODELVIEW);
        gl.glLoadIdentity();
        gl.glLoadMatrixd(matrix, 0);

        viewVector3d[0] = matrix[2];
        viewVector3d[1] = matrix[6];
        viewVector3d[2] = matrix[10];

        rotationAxis3d[0] = matrix[0] * inputHandler.getDxr() + matrix[1] * inputHandler.getDyr();
        rotationAxis3d[1] = matrix[4] * inputHandler.getDxr() + matrix[5] * inputHandler.getDyr();
        rotationAxis3d[2] = matrix[8] * inputHandler.getDxr() + matrix[9] * inputHandler.getDyr();

        // Rotate
        if (rAngle != 0) {
            gl.glRotated(rAngle, rotationAxis3d[0], rotationAxis3d[1], rotationAxis3d[2]);
            gl.glGetDoublev(GL2.GL_MODELVIEW_MATRIX, matrix, 0);
            inputHandler.setDxr(0);
            inputHandler.setDyr(0);
        }

        if (this.inputHandler.mouseLeftClick) {
            pickElement(gl);
        }

        // render the scene of the elements
        elementRenderer.renderElements(gl, component.getElementList(), GL2.GL_RENDER);

        gl.glFlush();
    }

    @Override
    public void reshape(GLAutoDrawable glDrawable, int x, int y, int width, int height) {
        final GL2 gl = glDrawable.getGL().getGL2();

        // limit the minimum canvas width
        if (width < MINIMUM_WIDTH) {
            width = MINIMUM_WIDTH;
        }

        // limit the minimum canvas height
        if (height < MINIMUM_HEIGHT) {
            height = MINIMUM_HEIGHT;
        }

        // set the viewport to the window
        gl.glViewport(0, 0, width, height);

        aspectRatio = (double) (this.width * height) / (double) (this.height * width);

        this.width = width;
        this.height = height;
        this.viewLeft = this.viewLeft / aspectRatio;
        this.viewRight = this.viewRight / aspectRatio;

        gl.glMatrixMode(GL2.GL_PROJECTION);
        gl.glLoadIdentity();
        gl.glOrtho(viewLeft, viewRight, viewBottom, viewTop, viewNear, viewFar);
        gl.glMatrixMode(GL2.GL_MODELVIEW);
        gl.glLoadIdentity();

    }

    public void pickElement(GL2 gl) {
        int[] selectBuf = new int[SELECTION_BUFFER_SIZE];
        selectionBuffer = Buffers.newDirectIntBuffer(SELECTION_BUFFER_SIZE);
        viewport = new int[4];
        gl.glGetIntegerv(GL2.GL_VIEWPORT, viewport, 0);
        gl.glSelectBuffer(SELECTION_BUFFER_SIZE, selectionBuffer);
        gl.glRenderMode(GL2.GL_SELECT);
        gl.glInitNames();
        gl.glMatrixMode(GL2.GL_PROJECTION);
        gl.glPushMatrix();
        gl.glLoadIdentity();
        glu.gluPickMatrix((double) inputHandler.mouseX, (double) (viewport[3] - inputHandler.mouseY), SELECTION_WINDOW_X, SELECTION_WINDOW_Y, viewport, 0);
        gl.glOrtho(viewLeft, viewRight, viewBottom, viewTop, viewNear, viewFar);
        elementRenderer.renderElements(gl, component.getElementList(), GL2.GL_SELECT);
        gl.glPopMatrix();
        gl.glFlush();
        hits = gl.glRenderMode(GL2.GL_RENDER);
        selectionBuffer.put(selectBuf);
        processHits(hits, selectBuf);

    }

    private void processHits(int hits, int buffer[]) {
        selection = new Selection[hits];
        int hit = 0;
        int i = 0;
        int n;
        int z1;
        int z2;
        int j;
        while (hit < hits) {
            n = buffer[i];
            i++;
            z1 = buffer[i];
            i++;
            z2 = buffer[i];
            i++;
            selection[hit] = new Selection(n, z1, z2);
            for (j = 0; j < n; j++) {
                selection[hit].setName(j, buffer[i]);
                i++;
            }
            hit++;
        }
        getSelectedElements(selection);

    }

    private void getSelectedElements(Selection[] selection) {
        System.out.println(" selection: ");
        System.out.println(Arrays.toString(selection));
        if (selection.length > 0) {
            Element selectedElement = getElement(selection[0].getNames());
            System.out.println(" element: ");
            System.out.println(selectedElement);
        }

    }

    private Element getElement(int[] elementIds) {
        if (elementIds.length > 0) {
            return component.getElement(elementIds[0]);
        }
        return null;

    }

}
